package com.example.wac.service.impl;

import com.example.wac.constant.Constant;
import com.example.wac.entity.system.LoginInfo;
import com.example.wac.enums.ErrorCode;
import com.example.wac.service.LoginService;
import com.example.wac.service.LoginUserService;
import com.example.wac.util.CommonUtils;
import com.example.wac.vo.ApiResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.crypto.hash.Sha256Hash;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;

/**
 * @author wuancheng
 * @description
 * @date 2022-04-24 17:46:00
 */

@Slf4j
@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    LoginUserService loginUserService;

    @Override
    public ApiResult login(Map<String, Object> dto, HttpServletRequest request) {
        String loginName = (String) dto.get("loginName");
        String pwd = (String) dto.get("password");
        String captcha = (String) dto.get("captcha");
        boolean rememberMe = CommonUtils.isTrue((String) dto.get("rememberMe"));
        if (StringUtils.isAnyBlank(loginName, pwd, captcha)) {
            throw ErrorCode.PARAM_CANNOT_EMPTY.getAppException();
        }
        String loginKey = String.format(Constant.RedisKey.LOGIN_USER, loginName);
        doLoginCheck(loginName, pwd, loginKey);
        String localCaptcha = (String) SecurityUtils.getSubject().getSession().getAttribute("KAPTCHA_SESSION_KEY");
        if (!captcha.equalsIgnoreCase(localCaptcha)) {
            SecurityUtils.getSubject().getSession().removeAttribute("KAPTCHA_SESSION_KEY");
            throw ErrorCode.CAPTCHA_ERROR.getAppException();
        }
        // 用户登录
        UsernamePasswordToken token = new UsernamePasswordToken(loginName, pwd, rememberMe, request.getRemoteHost());
        try {
            Subject subject = SecurityUtils.getSubject();
            // 若用户已登录则退出登录重新登录
            if (subject.isAuthenticated()){
                subject.logout();
            }
            if (!subject.isAuthenticated()){
                subject.login(token);
            }
        } catch (UnknownAccountException e) {
            token.clear();
            throw ErrorCode.LOGIN_INFO_NOT_EXISTS.getAppException();
        } catch (UnknownSessionException e) {
            token.clear();
            throw ErrorCode.SESSION_TIMEOUT_ERROR.getAppException();
        } catch (IncorrectCredentialsException e) {
            token.clear();
            throw ErrorCode.INCORRECT_CREDENTIALS_ERROR.getAppException();
        } finally {
            // 移除图片验证码
            SecurityUtils.getSubject().getSession().removeAttribute("KAPTCHA_SESSION_KEY");
        }
        // 登录成功时去除登录key
        redisTemplate.delete(loginKey);
        return ApiResult.success("登录成功");
    }

    /**
     * 防止暴力解密登录
     */
    private void doLoginCheck(String loginName, String pwd, String loginKey){
        if (StringUtils.isBlank(loginName)){
            return;
        }
        // 30分钟内用户名密码错误达三次以上1小时禁止登录否则达5次账号锁定
        if (!redisTemplate.hasKey(loginKey)){
            redisTemplate.opsForValue().set(loginKey, 0, 30, TimeUnit.MINUTES);
        }
        int errorCount = (int) redisTemplate.opsForValue().get(loginKey);
        boolean isLocked = errorCount >= 5 ? true : false;
        if ( errorCount >= 3){
            if (isLocked){
                throw ErrorCode.EXCEED_TIME_FORBIDDEN_ERROR.getAppException(5);
            }else {
                redisTemplate.opsForValue().increment(loginKey, 1);
                throw ErrorCode.RESTRICTED_TIME_FORBIDDEN_LOGIN_ERROR.getAppException();
            }
        }
        LoginInfo loginUser = loginUserService.findUserByLoginName(loginName);
        if ((loginUser == null) || (!Objects.equals(loginUser.getMobile(), loginName)) || (!Objects.equals(loginUser.getPwd(), new Sha256Hash(pwd, loginUser.getSalt(),2).toHex()))){
            redisTemplate.opsForValue().increment(loginKey, 1);
            throw ErrorCode.INCORRECT_CREDENTIALS_ERROR.getAppException();
        }
    }
}
